#-------------------------------------------------------------------------------
# Name:        converter.py
# Purpose:     Classes to convert presentation files to the slidespeech internal 
#              data structure
#       
# Authors:      Glenn Ramsey <glenn.ramsey@slidespeech.com>
#
# Copyright:   (c) Slidespeech Ltd 2012
# Licence:     MIT license
#-------------------------------------------------------------------------------

import os, shutil
import odp2script
from tempfile import mktemp
import glob
import zipfile
import json
from collections import namedtuple
from jinja2 import Environment, FileSystemLoader
import gettext
import jinja2.ext

from audio_converter import textToSpeech, AudioConfig
from video_converter import ConvertToVideo

from converter_error import ConverterError

# placeholder translation function
def _():
    lambda x:x

class Converter:
    """Base class for presentation converters"""
    # Public interface
    def __init__(self, previous):
        self._previous = previous
        self._debug = False
        
    def convert(self):
        """Compose a chain of converters and then call the _convertImpl function of
           each one in order"""
        self._chain = []
        o = self
        while not o.previous() is None:
            self._chain.insert(0,o)
            o = o.previous()
        self._chain.insert(0,o)
        
        for o in self._chain:
            o._convertImpl()
        
        self.cleanUp()
        
    def cleanUp(self):
        """Call the _cleanUpImpl function of each object in the chain in reverse order"""
        self._chain.reverse()
        
        for o in self._chain:
            o._cleanUpImpl()

        if not self._debug:
            # remove the temporary dir
            tmp = self.tempDir(False)
            try:
                if not tmp is None:
                    shutil.rmtree(tmp)
            except IOError as e:
                ex = ConverterError("Could not remove temporary directory", e)
                raise ex
        
    def previous(self):
        return self._previous
    
    def _convertImpl(self):
        """Derived classes must override this""" 
        raise NotImplementedError()

    def _cleanUpImpl(self):
        """Derived classes should override this if they have clean up to do""" 
        pass
    
    def script(self):
        if not self._previous is None:
            return self._previous.script()
        # FIXME: we could create the script structure here
        ex = ConverterError("Script has not been defined. Add a *ScriptConverter to the decorator chain.")
        raise ex

    def slideList(self):
        # ideally we should have an abstraction for slides, in case they are not PNG images
        if not self._previous is None:
            return self._previous.slideList()
        return None
    
    def tempDir(self, create=True, parent=None):
        #print self.__class__.__name__, self.tempDir.__name__

        # Search the decorator chain for a tempDir, if one doesn't exist
        # then create one in this object.
        
        if hasattr(self, "_tempDir"):
            return self._tempDir
        else:
            foundDir = None
            if not self._previous is None:
                foundDir = self._previous.tempDir(create, self)
    
            if foundDir:
                return self._previous.tempDir(False)
            elif create and parent is None:
                if self._debug:
                    self._tempDir = os.path.join(os.getcwd(), "tmp")
                else:
                    self._tempDir = mktemp()
                try:
                    os.makedirs(self._tempDir)
                except OSError:
                    # dir already exists, shouldn't happen
                    pass
                return self._tempDir
    
    # Protected interface
    def _convertPrevious(self):
        if not self._previous is None:
            self._previous.convert()

    def _cleanUpPrevious(self):
        if not self._previous is None:
            self._previous.cleanUp()
        else:
            Converter.cleanUp(self)
            
#class DummyConverter(Converter):
#    def __init__(self, previous):
#        Converter.__init__(self, previous)
#            
#    def _convertImpl(self):
#        pass
#    
#    def _cleanUpImpl(self):
#        pass

class ImageConverter(Converter):
    def __init__(self, imageDir, previous):
        Converter.__init__(self, previous)
        self._imageDir = imageDir
            
    def _convertImpl(self):
        # Get all of the files in the image dir, assume that they are named
        # alphanumerically.
        self._slideList = sorted(glob.glob(os.path.join(self._imageDir, "*")))
                
        # add the image file locations to the script
        try:
            seq = self.script()['S']

            # check that we have the same number of images as notes
            if len(self._slideList) != len(seq):
                ex = ConverterError(_("Number of images and slides is different"))
                raise ex
            
            for index, slide in enumerate(seq):
                slide['i'] = self._slideList[index]
            
        except KeyError as e:
            ex = ConverterError(_("Field missing in slide script"), e)
            raise ex

    def _cleanUpImpl(self):
        pass

class ODPScriptConverter(Converter):
    def __init__(self, odpFileName, language, previous=None):
        Converter.__init__(self, previous)
        self._odpFileName = odpFileName
        self._language = language
        self._script = None
            
    def _convertImpl(self):

        notesList = odp2script.extractNotes(self._odpFileName)
        s = {}
        # the top level is a Sequence
        seq = []
        for i in range(len(notesList)):
            q = {}
            q['t'] = "Slide{0}".format(i)
            q['l'] = self._language
            q['q'] = notesList[i]
            # absolute path of image file
            # q['i'] = imageFileList[i]
            seq.append(q)
        s['S'] = seq
        
        self._script = s
            
    def script(self):
        return self._script


class AudioConverter(Converter):
    def __init__(self, previous=None):
        Converter.__init__(self, previous)    
    
    def _convertImpl(self):
        
        path = os.path.join(self.tempDir(), "audio")
        try:
            os.makedirs(path)
        except OSError:
            # dir already exists, shouldn't happen
            pass
        
        prefix = 'Slide'

        textList = []
        try:
            seq = self.script()['S']
            # Extract the speaker notes from the slides and add to a list
            for slide in seq:
                notes = slide['q']
                lang = slide['l']
                AudioText = namedtuple('Text', 'text language')
                textList.append(AudioText(notes, lang))
            
        except KeyError as e:
            ex = ConverterError(_("Field missing in slide script"), e)
            raise ex

        # create the audio files
        textToSpeech(textList, path, prefix)


class ZippedJSONConverter(Converter):
    # FIXME: this is not generic enough
    # It should proabably just zip everything in a given directory
    def __init__(self, zipFilePath, previous=None):
        Converter.__init__(self, previous)    
        self._zipFilePath = zipFilePath
        
    def _convertImpl(self):

        tmp = self.tempDir()
        
        z = zipfile.ZipFile(self._zipFilePath, 'w')
        
        try:
                        
            seq = self.script()['S']
            # Write the image file to the zip and modify the path to reflect its
            # location in the zip file.
            for slide in seq:
                imagePath = slide['i']
                (head, tail) = os.path.split(imagePath)
                z.write(imagePath, "images/"+tail, zipfile.ZIP_DEFLATED)
                # update the script image path to reflect its location in the zip archive
                slide['i'] = "images/"+tail
                
            
        except KeyError:
            pass

        # add audio if it exists
        # FIXME: should the audio be included in the script        
        audioPath = os.path.join(tmp, "audio")
        if os.path.exists(audioPath):
            audioFiles = glob.glob(os.path.join(audioPath, "*.ogg"))
            audioFiles.extend(glob.glob(os.path.join(audioPath, "*.mp3")))
        
            for af in audioFiles:
                (head, tail) = os.path.split(af)
                z.write(af, "audio/"+tail, zipfile.ZIP_DEFLATED)
            
        
        jsonString = json.dumps(self.script())
        z.writestr("script.json", jsonString, zipfile.ZIP_DEFLATED)
        
        z.close()
        
    
    def cleanUp(self):
        # do local cleanup before the previous class
        self._cleanUpPrevious()

        
class ZippedHTML5Converter(Converter):
    def __init__(self, zipFilePath, templateDir, previous=None):
        Converter.__init__(self, previous)    
        self._zipFilePath = zipFilePath
        self._templateDir = templateDir 

    def _convertImpl(self):
        
        # structure we need to create
        # index.html
        # inlcludes
        # source
        #    slide0
        #        index.hmtl
        #        css
        #            styles.css
        #        media
        #            Slide.ogg
        #            Slide.mp3
        #            slide-background.png
        #            slide-content.png
        #    slide1
        #        ...
        
        tmp = self.tempDir()
        audioDir = os.path.join(tmp, "audio")
        
        # FIXME get language from config
        language = 'en'
        
        env = Environment(loader=FileSystemLoader(self._templateDir), extensions=['jinja2.ext.i18n'])    
        domain = 'slidespeech'
        try:
            trans = gettext.translation(domain, os.path.join(os.getcwd(), 'locale'), [language])
        except IOError:
            # This probably means that there is no .mo file.
            # FIXME: This needs to be generated during installation
            trans = gettext.NullTranslations()
    
        env.install_gettext_translations(trans)
        
        z = zipfile.ZipFile(self._zipFilePath, 'w', zipfile.ZIP_DEFLATED)

        try:
            seq = self.script()['S']
        
            template = env.get_template('main_index.html')
            indexStr = template.render(LANGUAGE=language,
                                       slideNumbers=range(1, 1+len(seq)),
                                       ttsURL=AudioConfig.ttsURL,
                                       ttsEngine=AudioConfig.ttsEngine
                                       )
                        
            print range(1, 1+len(seq))
            z.writestr("html/index.html", indexStr.encode('utf-8'))
            
            # copy the includes dir
            def visit(arg, dirname, names):
                arcdir = os.path.relpath(dirname, arg)
                for name in names:
                    if not os.path.isdir(os.path.join(dirname, name)):
                        z.write(os.path.join(dirname, name), "html/"+arcdir+"/"+name )
                
            os.path.walk(os.path.join(self._templateDir, "includes"), visit, self._templateDir)

            for i, slide in enumerate(seq, 1):
                htmlTempl = env.get_template('slide.html')
                htmlStr = htmlTempl.render(LANGUAGE=language)

                # create index.html and css/sytles.css and copy the audio and 
                arcpath = "html/source/slide{0}".format(i)
                z.writestr(arcpath+"/index.html", htmlStr.encode('utf-8'))

                cssTempl = env.get_template('styles.css')
                cssStr = cssTempl.render(LANGUAGE=language, slideImageName="slide-content.png")
                # create index.html and css/sytles.css and copy the audio and 
                z.writestr(arcpath+"/css/styles.css", cssStr.encode('utf-8'))
                
                for suffix in [".ogg", ".mp3"]:
                    z.write(os.path.join(audioDir, "Slide{0}{1}".format(i, suffix)), arcpath+"/media/Slide{0}".format(suffix))

                print slide['i']
                z.write(slide['i'], arcpath+"/media/slide-content.png")
              
        except KeyError as e:
            ex = ConverterError(_("Field missing in slide script"), e)
            raise ex
        
        
class VideoConverter(Converter):
    """
    Wrapper around video_converter.ConvertToVideo().
    """
        
    def __init__(self, out_file, previous=None):
        """
        @param out_file: Location of the target output file name
        """
        Converter.__init__(self, previous=previous)
        
        self.out_file = out_file
        self.audio_dir = "audio"
        self.video_dir = "video"
        
    def _convertImpl(self):
        """
        Exports the slide images and the audio files into a movie file.
        """
        
        # test if audio files were created 
        if not os.path.exists(os.path.join(self.tempDir(),"audio")):
            raise ConverterError("Audio files were not created yet! "
                                + "Add an AudioConverter to the decorator chain!")

        # load temporary audio and video directory        
        audio_dir = os.path.join(self.tempDir(), self.audio_dir)
        video_dir = os.path.join(self.tempDir(), self.video_dir)
        
        try:
            os.makedirs(video_dir)
        except OSError:
            # dir already exists, shouldn't happen
            pass

        # retrieve image files
        seq = self.script()['S']
        l_img = [ slide['i'] for slide in seq ]
        
        # retrieve audio files
        # FIXME: Retrieving the audio files in this way is quite ugly...
        l_audio = glob.glob(os.path.join(audio_dir, "*.mp3"))
        l_audio.sort()

        # now call ConvertToVideo
        converter = ConvertToVideo(l_img, l_audio, video_dir, self.out_file)


def ConvertToHTML(infilename, outfilename):
    (base, suffix) = os.path.splitext(infilename)

    if not suffix.lower() in (".odp", ".ppt"):
        raise ConverterError(_("Can't convert %s files." % suffix))

    if suffix == ".odp":
        from odp_png_converter import ODPPngConverter 
        s = ODPScriptConverter(infilename, "en", d)
        p = ODPPngConverter(infilename, s)
    elif suffix == ".ppt":
        from ppt_png_converter import PPTConverter 
        p = PPTConverter(infilename, "en")
        
    a = AudioConverter(p)
    z = ZippedHTML5Converter(outfilename, os.path.join("templates"), a)
    
    z.convert()
    

if __name__ == "__main__":
    
    # let's do a simple test of the converter
    c = ODPScriptConverter("test/4slide-presentation.odp", "en")
    p = ImageConverter("test/slide_images", c)
    a = AudioConverter(p)
    v = VideoConverter("output.avi", a)
    v.convert()

    